﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using net.xBei.Helper;
//using Newtonsoft.Json;

namespace net.xBei.Models
{
    [ModelBinder(BinderType = typeof(LengthUnit.ModelBinder))]
    //[JsonConverter(typeof(LengthUnit.JsonConverter))]
    public struct LengthUnit : ICloneable,
        IEqualityComparer<LengthUnit>
    {

        public static readonly LengthUnit Empty;
        static Regex regex => new Regex("^(-{0,1}\\d+(\\.\\d+){0,1})(m|dm|cm|mm|in|ft|px|pt|%){0,1}$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private string strValue;
        private long intValue;
        private decimal number;
        public UnitType Unit { get; private set; }
        //public LengthUnit() : this(string.Empty) { }

        public LengthUnit(string value) {
            this.strValue = value?.Trim() ?? string.Empty;
            intValue = 0;
            number = 0m;
            Unit = UnitType.NONE;
            Parse();
        }
        public int GetInt() => intValue == 0 ? (int)number : (int)intValue;
        public long GetLong() => intValue == 0 ? (long)number : intValue;
        public decimal GetDecimal() => number == 0 ? intValue : number;
        public float GetFloat() => number == 0 ? intValue : (float)number;
        public double GetDouble() => number == 0 ? intValue : (double)number;
        public bool HasValue => Unit != UnitType.NONE;
        private void Parse() {
            if (string.IsNullOrWhiteSpace(strValue)) return;
            var m = regex.Match(strValue);
            if (m.Success == false)
                throw new ArgumentOutOfRangeException(strValue);
            var numValue = m.Groups[1].Value;
            var unitValue = m.Groups[3].Value;
            if (numValue.Contains(".")) {
                number = decimal.Parse(numValue);
            } else {
                intValue = long.Parse(numValue);
            }
            if (string.IsNullOrWhiteSpace(unitValue)) {
                if (numValue.Contains(".")) {
                    if (intValue == 100 || (number > 0 && number < 1)) {
                        Unit = UnitType.PERCENT;
                    } else {
                        Unit = UnitType.MILLIMETER;
                    }
                } else {
                    Unit = UnitType.PIEXL;
                }
            } else if (unitValue == "%") {
                Unit = UnitType.MILLIMETER;
            } else {
                Unit = unitValue.GetEnum<UnitType>(UnitType.NONE);
            }
        }
        public override string ToString() {
            if (Unit == UnitType.NONE) return string.Empty;
            var unit = "%";
            switch (Unit) {
                case UnitType.PIEXL:
                    unit = "px";
                    break;
                case UnitType.POINT:
                    unit = "pt";
                    break;
                case UnitType.MILLIMETER:
                    unit = "mm";
                    break;
                case UnitType.CENTIMETER:
                    unit = "cm";
                    break;
                case UnitType.DECIMETRE:
                    unit = "dm";
                    break;
                case UnitType.METER:
                    unit = "m";
                    break;
                case UnitType.INCH:
                    unit = "in";
                    break;
                case UnitType.FEET:
                    unit = "ft";
                    break;
            }
            return $"{(intValue == 0 ? number : intValue)}{unit}";
        }
        public object Clone() {
            return new LengthUnit(strValue);
        }
        private const float MMIN = 0.3937008F;
        private const float INMM = 25.4F;
        private const float IN2MM = 25.4F;
        private static float In2Mm(float v) => v / MMIN * 10;
        private static float Px2Mm(float v, float dpi) => In2Mm(v / dpi);
        private static float PixelToMillimeter(float pixel, int dpi) => pixel * IN2MM / dpi;
        private static float PtToPixel(float point, int dpi) => point / 72 * dpi;
        public float GetCentimeter(int dpi = 75) {
            if (Unit == UnitType.CENTIMETER)
                return GetFloat();
            if (Unit == UnitType.MILLIMETER)
                return GetFloat() / 10;
            if (Unit == UnitType.DECIMETRE)
                return GetFloat() * 10;
            if (Unit == UnitType.METER)
                return GetFloat() * 100;
            if (Unit == UnitType.INCH)
                return GetFloat() * INMM / 10F;
            if (Unit == UnitType.FEET)
                return GetFloat() * INMM * 1.2F;
            if (Unit == UnitType.PIEXL)
                return PixelToMillimeter(GetFloat(), dpi) * 10;
            if (Unit == UnitType.POINT)
                return PixelToMillimeter(PtToPixel(GetFloat(), dpi), dpi) * 10;
            throw new Exception("不支持");
        }
        public float GetMillimeter(int dpi = 75) {
            if (Unit == UnitType.CENTIMETER)
                return GetFloat() * 10;
            if (Unit == UnitType.MILLIMETER)
                return GetFloat();
            if (Unit == UnitType.DECIMETRE)
                return GetFloat() * 100;
            if (Unit == UnitType.METER)
                return GetFloat() * 1000;
            if (Unit == UnitType.INCH)
                return GetFloat() * INMM;
            if (Unit == UnitType.FEET)
                return GetFloat() * INMM * 12F;
            if (Unit == UnitType.PIEXL)
                return PixelToMillimeter(GetFloat(), dpi);
            if (Unit == UnitType.POINT)
                return PixelToMillimeter(PtToPixel(GetFloat(), dpi), dpi);
            throw new Exception("不支持");
        }
        public bool Equals(LengthUnit x, LengthUnit y) {
            return string.Equals(x.strValue, y.strValue, StringComparison.OrdinalIgnoreCase);
        }

        public int GetHashCode(LengthUnit obj) {
            return obj.strValue.ToLower().GetHashCode();
        }
        public enum UnitType
        {
            NONE = 0,
            /// <summary>
            /// 像素
            /// </summary>
            PIEXL = 1,
            PX = 1,
            POINT = 2,
            PT = 2,
            /// <summary>
            /// 百分比
            /// </summary>
            PERCENT = 3,
            /// <summary>
            /// 毫米
            /// </summary>
            MILLIMETER = 4,
            MM = 4,
            /// <summary>
            /// 厘米
            /// </summary>
            CENTIMETER = 5,
            CM = 5,
            /// <summary>
            /// 分米
            /// </summary>
            DECIMETRE = 6,
            DM = 6,
            /// <summary>
            /// 米
            /// </summary>
            METER = 7,
            M = 7,
            /// <summary>
            /// 英寸
            /// </summary>
            INCH = 8,
            IN = 8,
            /// <summary>
            /// 英尺
            /// </summary>
            FEET = 9,
            FT = 9,
        }
        
        #region 操作符重载
        //public static bool operator ==(LengthUnit left, LengthUnit right)=>
        public static bool operator >(LengthUnit left, int right) {
            return left.GetInt() > right;
        }
        public static bool operator <(LengthUnit left, int right) {
            return left.GetInt() < right;
        }
        public static bool operator >=(LengthUnit left, int right) {
            return left.GetInt() >= right;
        }
        public static bool operator <=(LengthUnit left, int right) {
            return left.GetInt() <= right;
        }
        #endregion

        #region 转换器
        //public class JsonConverter : Newtonsoft.Json.JsonConverter<LengthUnit>
        //{
        //    public override LengthUnit ReadJson(JsonReader reader, Type objectType, LengthUnit existingValue, bool hasExistingValue, JsonSerializer serializer)
        //        => new LengthUnit(reader.Value?.ToString() ?? string.Empty);

        //    public override void WriteJson(JsonWriter writer, LengthUnit value, JsonSerializer serializer)
        //        => writer.WriteValue(value.ToString());
        //}
        public class ModelBinder : IModelBinder
        {
            public Task BindModelAsync(ModelBindingContext bindingContext) {
                //var modelTypeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
                if (bindingContext == null) {
                    throw new ArgumentNullException(nameof(bindingContext));
                }

                var modelName = bindingContext.ModelName;

                // Try to fetch the value of the argument by name
                var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);

                if (valueProviderResult == ValueProviderResult.None) {
                    return Task.CompletedTask;
                }

                bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

                var value = valueProviderResult.FirstValue;


                if (string.IsNullOrEmpty(value)) {
                    //return Task.CompletedTask;
                    value = string.Empty;
                }

                bindingContext.Result = ModelBindingResult.Success(new LengthUnit(value));
                return Task.CompletedTask;
            }
        }
        #endregion
    }
}
